#!/usr/bin/python
#-*- coding: utf-8 -*-
import shutil
import signal
from syslog import *
from optparse import OptionParser
from threading import Event
import sys, time, re, urllib, os
from traceback import format_exc
from subprocess import Popen, PIPE, call
from os.path import exists, isdir, getmtime, abspath, join, isfile


def become_daemon(out="/dev/null", err="/dev/null", home_dir=".", umask=022):
    # First fork
    try: 
        if os.fork() > 0: 
            sys.exit(0)
    except OSError, e:
        sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
        sys.exit(1)
    os.setsid()
    os.chdir(home_dir)
    os.umask(umask)

    # Second fork
    try: 
        if os.fork() > 0: 
            os._exit(0)
    except OSError, e:
        sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
        os._exit(1)
    si = open('/dev/null', 'r') 
    so = open(out, 'a+', 0)
    se = open(err, 'a+', 0)
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())
    # Set custom file descriptors so that they get proper buffering.
    sys.stdout, sys.stderr = so, se


class SSHMirror:
    def __init__(self, user, host, delete=False):
        self.user = user
        self.host = host
        self.delete = delete

    def getFile(self, src, dst, mtime):
        try:
            shutil.rmtree(dst, True)
            args = "scp %s@%s:%s %s"%(self.user, self.host, src, dst)
            print args
            r = call(args, shell=True)
            if r == 0:
                os.utime(dst, (mtime, mtime))
            else:
                raise Exception("get file fail: %s"%args)
        except:
            syslog(LOG_ERR, format_exc())
            return False
        return True

    def __call__(self, src, dst):
        success = True
        try:
            target = "%s@%s"%(self.user, self.host)
            cmd = '"cd %s;stat -t . *"'%src
            args = " ".join(["ssh", target, cmd])
            #print args
            f = Popen(args, shell=True, stdout=PIPE, stderr=PIPE)
            lines = f.stdout.readlines()
            if self.delete and isdir(dst):
                remote_files = [line.split()[0] for line in lines]
                if remote_files: remote_files.pop(0)
                local_files = set(os.listdir(dst))
                for name in local_files.difference(remote_files):
                    path = join(dst, name)
                    if isfile(path):
                        os.remove(path)
                    else:
                        shutil.rmtree(path)
            mtime = None
            # deal with child files and directories
            for i, line in enumerate(lines):
                stats = line.split()
                #print stats
                try:
                    name = stats[0]
                    ftype = int(stats[8])
                    mt = int(stats[-3])
                except (ValueError, IndexError), e:
                    syslog(LOG_CRIT, format_exc())
                    if i==0:
                        return
                    else:
                        continue
                if i == 0:
                    mtime = mt
                    if exists(dst) and mt == int(getmtime(dst)):
                        return
                    if isfile(dst):
                        os.remove(dst)
                    if not exists(dst):
                        os.makedirs(dst)
                        continue
                if name.startswith("."):
                    continue
                lpath = join(dst, name)
                if not exists(lpath) or int(getmtime(lpath)) != mt:
                    if ftype>1:
                        if not self(src+name+"/", lpath):
                            success = False
                    else:
                        if not self.getFile(src+name, lpath, mt):
                            success = False
            if success and mtime:
                os.utime(dst, (mtime,mtime))
        except:
            syslog(LOG_ERR, format_exc())
            return False
        return success


class Server:
    def __init__(self):
        self._stop_event = Event()

    def start(self):
        parser = OptionParser(usage="%prog [-f] <src> <dst>")
        parser.add_option("-d", "--daemon",
                dest="daemon",
                action="store_true",
                default=False,
                help="run the script as a daemon")
        parser.add_option("-q", "--quiet",
                dest="quiet",
                action="store_true",
                default=False,
                help="don't print log message")
        parser.add_option("-e", "--delete",
                dest="delete",
                action="store_true",
                default=False,
                help="synchronous delete files")
        parser.add_option("-s", "--interval=",
                dest="interval",
                metavar="seconds",
                type="int",
                default=60,
                help="the interval seconds between scaning the server")
        options, args = parser.parse_args()
        if len(args) != 2:
            parser.print_usage()
            sys.exit()

        mask = LOG_CONS|LOG_PID
        if not options.quiet:
            mask = mask|LOG_PERROR
        openlog("sshmonitor", mask)
        src = args[0]
        dst = abspath(args[1])
        user_host, src = src.split(":")
        if src[-1] != "/": src += "/"
        user, host = user_host.split("@")
        if options.daemon:
            become_daemon()
        signal.signal(signal.SIGINT, self.stop)
        signal.signal(signal.SIGTERM, self.stop)
        mirror = SSHMirror(user, host, options.delete)
        while not self._stop_event.isSet():
            mirror(src, dst)
            self._stop_event.wait(options.interval)

    def stop(self, signum, frame):
        self._stop_event.set()


if __name__ == "__main__":
    server = Server()
    server.start()
